///
/// Global constants
///
const SAFARI_USER_AGENT = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-us) AppleWebKit/533.18.1 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5";


const REGEX_QUERY_PARAMETER_NAME_VALUE_PAIR = '[\\?&]([^=]+)=([^&\'"]*)';


///
/// Global variables
///

// Flag indicating whether debug logging is enabled
var ___debug_logging_enabled = false;



//
// Forward declarations of functions
//
var assert;


function debugLog(message)
{
    if (___debug_logging_enabled)
    {
        console.log(message);
    }
}


///
/// Log the error message
///
function logError(message)
{
    assert(false, message);
    
    // TODO: implement some kind of persistent log
}


///
/// Traditional debug assert function
///
function assert(exp, message)
{
    if (!exp)
    {
        alert(message);
    }
}


///
/// Create a new <script> element with its source code included within the element
///
function createScriptInline(script)
{
    var newScript = document.createElement("script");
    newScript.type = "text/javascript";
    newScript.innerText = "script";
    
    return newScript;
}


///
/// Create a new <script> element with its source code loaded from the given URL
///
function createScriptRemote(sourceUrl)
{
    var newScript = document.createElement("script");
    newScript.type = "text/javascript";
    newScript.src = sourceUrl;

    return newScript;
}


///
/// Load a script into the document's <head> element from a third party web site
///
function loadScript(url)
{
    var headID = document.getElementsByTagName("head")[0];         
    var newScript = createScriptRemote(url);
    headID.appendChild(newScript);
}


///
/// Get an element by its ID in a cross-browser compatible way
///
function getElementByIdCompatible (the_id) 
{
    var elem = null;
    
    if ("string" != typeof(the_id))
    {
        elem = the_id;
    }
    else if (undefined != typeof(getElementByIdCompatible))
    {
        elem = document.getElementById(the_id);
    }
    else if (undefined != typeof(document.all))
    {
        elem = document.all[the_id];
    }
    else if (undefined != typeof(document.layers))
    {
        elem = document.layers[the_id];
    }
    else
    {
        assert(false, ("Unable to find element with id " + the_id));
    }
    
    return elem;
}


///
/// Get all elements with the given name and type that are contained within the given parent node
///
function getElementsByTagNameAndType(parent, tagName, typeName)
{
    debugLog('Entering getElementsByTagNameAndType');
    
    var allElements = parent.getElementsByTagName(tagName);
    var foundElements = new Array();    
    
    for (var i=0; i < allElements.length; i++)
    {
        if (typeName == allElements[i].type)
        {
            foundElements.push(allElements[i]);
        }
    };    
    
    debugLog('Exiting getElementsByTagNameAndType');
    
    return foundElements;
}

Array.prototype.getUnique = function ()
{
    debugLog('Entering Array.getUnique');
    
    var dictionary = new Object();
    var key;
    for (var i = 0; i < this.length; i++)
    {
        key = this[i];
        dictionary[key] = 1;
    };

    var uniqueValues = new Array();
    for (key in dictionary)
    {
        uniqueValues.push (key);
    };
    
    debugLog('Exiting Array.getUnique');
    return uniqueValues;
} 

function buildRegExpAlternation(alternateValues)
{
    debugLog('Entering buildRegExpAlternation');
    
    var alternation = "";

    for (var index = 0; index < alternateValues.length; index++)
    {
        if (0 < index)
        {
            alternation = alternation + '|';
        }
        alternation = alternation + alternateValues[index];
    }
    
    debugLog('Exiting buildRegExpAlternation');
    
    return alternation;
}

function buildQueryParametersRegExp(queryParameterNames)
{
    debugLog('Entering buildQueryParametersRegExp');
    
    var queryTemplate = "";
    var nameAlternation = buildRegExpAlternation(queryParameterNames);

    for (var count = 0; count < queryParameterNames.length; count++)
    {
        var delimiter = (0 == count) ? '\\?' : '&';
        
        queryTemplate += delimiter + '(?:' + nameAlternation + ')' + '=([^&]*)';
    }
    
    debugLog('Exiting buildQueryParametersRegExp');
    
    return new RegExp(queryTemplate);
}

///
/// Finds all anchors on which the href contains all of the specified query parameter names
///
function getAnchorsWithQueryParameters(anchors, queryParameterNames)
{
    debugLog('Entering getAnchorsWithQueryParameters');
    
    var matchingAnchors = new Array();

    var regex = buildQueryParametersRegExp(queryParameterNames);

    for (var index = 0; index < anchors.length; index++)
    {
        if (regex.test(anchors[index].href))
        {
            matchingAnchors.push(anchors[index]);
        }
    }
    
    debugLog('Exiting getAnchorsWithQueryParameters');
    
    return matchingAnchors;
}

///
/// Create a name-value pair dictionary containing all of the query parameter names and respective values
///
function getQueryParameters(href)
{
    debugLog('Entering getQueryParameters');
    
    var values = new Object();

    var regex = new RegExp(REGEX_QUERY_PARAMETER_NAME_VALUE_PAIR, 'ig');
    
    var match = regex.exec(href);
    while (null != match)
    {
        values[match[1]]=match[2];
        match = regex.exec(href);
    }
    
    debugLog('Exiting getQueryParameters');
    
    return values;
}

///
/// Extract a queuery parameter's value from an anchor's href property
///
function getQueryParameterValueFromAnchor(anchor, parameterName)
{
    debugLog('Entering getQueryParameterValueFromAnchor');
    
    var values = getQueryParameters(anchor.href);
    
    debugLog('Exiting getQueryParameterValueFromAnchor');
    
    return values[parameterName];
}

function getQueryParameterValueFromMultipleAnchors(anchors, parameterName)
{
    debugLog('Entering getQueryParameterValueFromMultipleAnchors');
    
    var values = new Array();
    
    for (var index = 0; index < anchors.length; index++)
    {
        var qpvalue = getQueryParameterValueFromAnchor(anchors[index], parameterName);
        if (null != qpvalue)
        {
            values.push(qpvalue);
        }
    }
    
    debugLog('Exiting getQueryParameterValueFromMultipleAnchors');
    
    return values;
}

function getMonthAsName(month)
{
    var months=new Array(12);
    months[0]="January";
    months[1]="February";
    months[2]="March";
    months[3]="April";
    months[4]="May";
    months[5]="June";
    months[6]="July";
    months[7]="August";
    months[8]="September";
    months[9]="October";
    months[10]="November";
    months[11]="December";
    
    return months[month];
}


function createHiddenForm(id, name)
{
    var newForm = document.createElement("form");
    newForm.id = id;
    newForm.name = name;
    newForm.method="POST";
    newForm.action="javascript:void(0)";
    
    return newForm;
}


function createHiddenInputField(id, name, value)
{
    var newInput = document.createElement("input");
    newInput.id = id;
    newInput.type = "hidden";
    newInput.name = name;
    newInput.value = value;
    
    return newInput;
}


///
/// Do a recursive dump of the given object's properties
///
/// Parameters:
///   object: The object being dumped
///   depth:  The recursion depth. Use 0 as the default value
///   max:    The maximum number of time to recurse the object
///
function odump(object, depth, max)
{
    var output = "";  

    try
    {
        depth = depth || 0;
        max = max || 2;
    
        if (depth > max)
        {
            debugLog("odump: early exit");
            return false;
        }
        
        var indent = "";
        for (var i = 0; i < depth; i++)
        {
            indent += "  ";
        }

        debugLog("odump: beginning for loop");
        for (var key in object)
        {
            debugLog("odump: initializing output string");
            output += "\n" + indent + key + ": ";
    
            switch (typeof(object[key]))
            {
                case "object":
                    debugLog("odump: recursing object");
                    output += odump(object[key], depth + 1, max);
                    break;
                case "function":
                    debugLog("odump: handling type 'function'");
                    output += "function";
                    break;
                default:
                    debugLog("odump: handling type 'default' (" + typeof(object[key]) + ")");
                    output += object[key];
                    break;
            }
        }
    }
    catch (err)
    {
        var txt = "There was an error on this page.\n\n";
        txt += "Error description: " + err.description + "\n\n";
        txt += "Click OK to continue.\n\n";
        console.log("odump: caught exception - " + txt);
        console.log("odump: current output string - '" + output + "'");
        console.log(odump(err, 0, 0));
        alert(txt);
    }

    debugLog("odump: normal exit");
    return output;
}

//
// Validates that the given controls really do exist
//
// pageMoniker: A user-friendly name for the page from which the controls were taken
// controls: An array of controls to be validated
function validateControls(pageMoniker, controls)
{
    var allControlsValid = true;
    
    for (var index = 0; index < controls.length; index++)
    {
        if ('undefined' == controls[index])
        {
            allControlsValid = false;
            break;
        }
    }

    if (!allControlsValid)
    {
        logError("ERROR - Controls not found on " + pageMoniker);
    }
    
    return allControlsValid;
}

function nodeListToArray(arr)
{
    var retVal = new Array(arr.length);
    
    for (var index = 0; index < arr.length; index++)
    {
        retVal[index] = arr[index];
    }
    
    return retVal;
}

function findRowIndexContaining(rows, year)
{
    for (var r in rows)
    {
        var txt = rows[r].innerText;
        if (null != txt)
        {
            if (txt.search(year) >= 0)
            {
                return r;
            }
        }
    }
    
    return -1;
}



